package com.roamer.rest.exception;

import com.roamer.rest.IErrorCode;
import com.roamer.rest.RestResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;

/**
 * 统一异常处理器
 *
 * @author roamer
 * @version V1.0
 * @date 2019/1/21 16:38
 */
@Slf4j
public class BaseExceptionHandler {

    /**
     * 缺少参数异常
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex,
                                                                          HttpServletRequest request) {
        log.error("handleMissingServletRequestParameterException start, uri:{}, caused by: ", request.getRequestURI(),
                  ex);
        return RestResult.failure(IErrorCode.C40001, new ParameterInvalidItem().setField(ex.getParameterName())
                                                                               .setMessage(ex.getMessage()));
    }

    /**
     * 参数解析异常
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex,
                                                                  HttpServletRequest request) {
        log.error("handleHttpMessageNotReadableException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C40002);
    }

    /**
     * 参数绑定异常
     * <p>
     * 处理器参数类型不匹配
     * </p>
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex,
                                                                      HttpServletRequest request) {
        log.error("handleHttpMessageNotReadableException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C40003);
    }

    /**
     * 参数验证异常
     * <p>
     * 非{@link org.springframework.web.bind.annotation.RequestBody} 标注的参数校验异常时
     * </p>
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleBindException(BindException ex, HttpServletRequest request) {
        log.error("handleBindException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        List<ParameterInvalidItem> parameterInvalidItemList = ValidateConvert.convertToInvalidItemList(
                ex.getBindingResult());
        return RestResult.failure(IErrorCode.C40004, parameterInvalidItemList);
    }


    /**
     * 参数验证异常
     * <p>
     * <p>
     * {@link org.springframework.web.bind.annotation.RequestBody} 标注的参数校验异常时
     * </p>
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex,
                                                                  HttpServletRequest request) {
        log.error("handleMethodArgumentNotValidException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        List<ParameterInvalidItem> parameterInvalidItemList = ValidateConvert.convertToInvalidItemList(
                ex.getBindingResult());
        return RestResult.failure(IErrorCode.C40004, parameterInvalidItemList);
    }

    /**
     * 参数验证异常
     * <p>
     * 继承自{@link ValidationException}，可以获取异常参数信息
     * </p>
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleConstraintViolationException(ConstraintViolationException ex,
                                                               HttpServletRequest request) {
        log.error("handleConstraintViolationException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        List<ParameterInvalidItem> parameterInvalidItemList = ValidateConvert.convertToInvalidItemList(
                ex.getConstraintViolations());
        return RestResult.failure(IErrorCode.C40004, parameterInvalidItemList);
    }

    /**
     * 参数验证异常
     * <p>
     * 400 - Bad Request
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleValidationException(ValidationException ex, HttpServletRequest request) {
        log.error("handleValidationException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C40004);
    }


    /**
     * 资源未找到异常
     * 404 - Not Found
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handlerNotFoundException(NoHandlerFoundException ex, HttpServletRequest request) {
        log.error("handlerNotFoundException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C40400);
    }

    /**
     * 不支持当前请求方法
     * 405 - Method Not Allowed
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex,
                                                                         HttpServletRequest request) {
        log.error("handleHttpRequestMethodNotSupportedException start, uri:{}, caused by: ", request.getRequestURI(),
                  ex);
        return RestResult.failure(IErrorCode.C40500);
    }

    /**
     * 请求的格式不可得
     * 406 - Not Acceptable
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException ex,
                                                                      HttpServletRequest request) {
        log.error("handleHttpMediaTypeNotAcceptableException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C40600);
    }

    /**
     * 不支持当前媒体类型
     * 415 - Unsupported Media Type
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean}
     */
    protected RestResult<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex,
                                                                     HttpServletRequest request) {
        log.error("handleHttpMediaTypeNotSupportedException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        return RestResult.failure(IErrorCode.C41500);
    }


    /**
     * 处理通用自定义业务异常
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResponseEntity<ResultBean<?>>}
     */
    protected ResponseEntity<RestResult<?>> handleBusinessException(BusinessException ex, HttpServletRequest request) {
        log.error("handleBusinessException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(),
                  ex.getClass(), ex.errorCode);
        RestResult<?> restResult = new RestResult<>(false, ex.code, ex.message, ex.getData());
        return ResponseEntity.status(ex.getErrorCode().httpStatus()).body(restResult);
    }

    /**
     * 处理运行时系统异常
     *
     * @param ex      异常信息
     * @param request 请求
     *
     * @return {@code ResultBean<?>}
     */
    protected RestResult<?> handleRuntimeException(RuntimeException ex, HttpServletRequest request) {
        log.error("handleRuntimeException start, uri:{}, caused by: ", request.getRequestURI(), ex);
        //TODO 可通过邮件、微信公众号等方式发送信息至开发人员、记录存档等操作
        return RestResult.failure(IErrorCode.ERROR);
    }


}
